{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "6fa6fb7f",
   "metadata": {},
   "source": [
    "# 쿼리 재작성 모듈 추가\n",
    "\n",
    "**절차**\n",
    "\n",
    "1. Naive RAG 수행\n",
    "2. 검색된 문서에 대한 관련성 체크(Groundedness Check)\n",
    "3. Web Search\n",
    "4. (이번 튜토리얼) Query Rewrite\n",
    "\n",
    "**참고**\n",
    "\n",
    "- 이전 튜토리얼에서 확장된 내용이므로, 겹치는 부분이 있을 수 있습니다. 부족한 설명은 이전 튜토리얼을 참고해주세요.\n",
    "\n",
    "![langgraph-query-rewrite](assets/langgraph-query-rewrite.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f21c872b",
   "metadata": {},
   "source": [
    "## 환경 설정"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "62c08760",
   "metadata": {},
   "outputs": [],
   "source": [
    "# !pip install -U langchain-teddynote"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "064d5c8c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "562b0043",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LangSmith 추적을 시작합니다.\n",
      "[프로젝트명]\n",
      "CH17-LangGraph-Structures\n"
     ]
    }
   ],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH17-LangGraph-Structures\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "06468c1c",
   "metadata": {},
   "source": [
    "## 기본 PDF 기반 Retrieval Chain 생성\n",
    "\n",
    "여기서는 PDF 문서를 기반으로 Retrieval Chain 을 생성합니다. 가장 단순한 구조의 Retrieval Chain 입니다.\n",
    "\n",
    "단, LangGraph 에서는 Retirever 와 Chain 을 따로 생성합니다. 그래야 각 노드별로 세부 처리를 할 수 있습니다.\n",
    "\n",
    "**참고**\n",
    "\n",
    "- 이전 튜토리얼에서 다룬 내용이므로, 자세한 설명은 생략합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "f905df18",
   "metadata": {},
   "outputs": [],
   "source": [
    "from rag.pdf import PDFRetrievalChain\n",
    "\n",
    "# PDF 문서를 로드합니다.\n",
    "pdf = PDFRetrievalChain([\"data/SPRI_AI_Brief_2023년12월호_F.pdf\"]).create_chain()\n",
    "\n",
    "# retriever와 chain을 생성합니다.\n",
    "pdf_retriever = pdf.retriever\n",
    "pdf_chain = pdf.chain"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d047f938",
   "metadata": {},
   "source": [
    "## State 정의\n",
    "\n",
    "`State`: Graph 의 노드와 노드 간 공유하는 상태를 정의합니다.\n",
    "\n",
    "일반적으로 `TypedDict` 형식을 사용합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de58d48d",
   "metadata": {},
   "source": [
    "이번에는 상태(State)에 관련성(relevance) 체크 결과를 추가합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1d7221f",
   "metadata": {},
   "source": [
    "**참고**\n",
    "\n",
    "- 이번에는 `question` 을 list 형식으로 정의합니다. 재작성된 Query 를 추가로 저장하기 위함입니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f19a3df5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Annotated, TypedDict, List\n",
    "from langgraph.graph.message import add_messages\n",
    "\n",
    "\n",
    "# GraphState 상태 정의\n",
    "class GraphState(TypedDict):\n",
    "    question: Annotated[List[str], add_messages]  # 질문(누적되는 list)\n",
    "    context: Annotated[str, \"Context\"]  # 문서의 검색 결과\n",
    "    answer: Annotated[str, \"Answer\"]  # 답변\n",
    "    messages: Annotated[list, add_messages]  # 메시지(누적되는 list)\n",
    "    relevance: Annotated[str, \"Relevance\"]  # 관련성"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c56d4095",
   "metadata": {},
   "source": [
    "## 노드(Node) 정의\n",
    "\n",
    "- `Nodes`: 각 단계를 처리하는 노드입니다. 보통은 Python 함수로 구현합니다. 입력과 출력이 상태(State) 값입니다.\n",
    "  \n",
    "**참고**  \n",
    "\n",
    "- `State`를 입력으로 받아 정의된 로직을 수행한 후 업데이트된 `State`를 반환합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "9ef0c055",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_teddynote.evaluator import GroundednessChecker\n",
    "from langchain_teddynote.messages import messages_to_history\n",
    "from langchain_teddynote.tools.tavily import TavilySearch\n",
    "from rag.utils import format_docs\n",
    "\n",
    "\n",
    "# 문서 검색 노드\n",
    "def retrieve_document(state: GraphState) -> GraphState:\n",
    "    # 질문을 상태에서 가져옵니다.\n",
    "    latest_question = state[\"question\"][-1].content\n",
    "\n",
    "    # 문서에서 검색하여 관련성 있는 문서를 찾습니다.\n",
    "    retrieved_docs = pdf_retriever.invoke(latest_question)\n",
    "\n",
    "    # 검색된 문서를 형식화합니다.(프롬프트 입력으로 넣어주기 위함)\n",
    "    retrieved_docs = format_docs(retrieved_docs)\n",
    "\n",
    "    # 검색된 문서를 context 키에 저장합니다.\n",
    "    return GraphState(context=retrieved_docs)\n",
    "\n",
    "\n",
    "# 답변 생성 노드\n",
    "def llm_answer(state: GraphState) -> GraphState:\n",
    "    # 질문을 상태에서 가져옵니다.\n",
    "    latest_question = state[\"question\"][-1].content\n",
    "\n",
    "    # 검색된 문서를 상태에서 가져옵니다.\n",
    "    context = state[\"context\"]\n",
    "\n",
    "    # 체인을 호출하여 답변을 생성합니다.\n",
    "    response = pdf_chain.invoke(\n",
    "        {\n",
    "            \"question\": latest_question,\n",
    "            \"context\": context,\n",
    "            \"chat_history\": messages_to_history(state[\"messages\"]),\n",
    "        }\n",
    "    )\n",
    "    # 생성된 답변, (유저의 질문, 답변) 메시지를 상태에 저장합니다.\n",
    "    return {\n",
    "        \"answer\": response,\n",
    "        \"messages\": [(\"user\", latest_question), (\"assistant\", response)],\n",
    "    }\n",
    "\n",
    "\n",
    "# 관련성 체크 노드\n",
    "def relevance_check(state: GraphState) -> GraphState:\n",
    "    # 관련성 평가기를 생성합니다.\n",
    "    question_answer_relevant = GroundednessChecker(\n",
    "        llm=ChatOpenAI(model=\"gpt-4o-mini\", temperature=0), target=\"question-retrieval\"\n",
    "    ).create()\n",
    "\n",
    "    # 관련성 체크를 실행(\"yes\" or \"no\")\n",
    "    response = question_answer_relevant.invoke(\n",
    "        {\"question\": state[\"question\"][-1].content, \"context\": state[\"context\"]}\n",
    "    )\n",
    "\n",
    "    # 참고: 여기서의 관련성 평가기는 각자의 Prompt 를 사용하여 수정할 수 있습니다. 여러분들의 Groundedness Check 를 만들어 사용해 보세요!\n",
    "    return {\"relevance\": response.score}\n",
    "\n",
    "\n",
    "# 관련성 체크하는 함수(router)\n",
    "def is_relevant(state: GraphState) -> GraphState:\n",
    "    if state[\"relevance\"] == \"yes\":\n",
    "        return \"relevant\"\n",
    "    else:\n",
    "        return \"not relevant\"\n",
    "\n",
    "\n",
    "# Web Search 노드\n",
    "def web_search(state: GraphState) -> GraphState:\n",
    "    # 검색 도구 생성\n",
    "    tavily_tool = TavilySearch()\n",
    "\n",
    "    search_query = state[\"question\"]\n",
    "\n",
    "    # 다양한 파라미터를 사용한 검색 예제\n",
    "    search_result = tavily_tool.search(\n",
    "        query=search_query,  # 검색 쿼리\n",
    "        topic=\"general\",  # 일반 주제\n",
    "        max_results=6,  # 최대 검색 결과\n",
    "        format_output=True,  # 결과 포맷팅\n",
    "    )\n",
    "\n",
    "    return {\"context\": search_result}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50857077",
   "metadata": {},
   "source": [
    "## Query Rewrite 노드 추가\n",
    "\n",
    "Query 를 재작성하는 프롬프트를 활용하여 기존의 질문을 재작성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "5885a0eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import PromptTemplate\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "\n",
    "# Query Rewrite 프롬프트 정의\n",
    "re_write_prompt = PromptTemplate(\n",
    "    template=\"\"\"Reformulate the given question to enhance its effectiveness for vectorstore retrieval.\n",
    "\n",
    "- Analyze the initial question to identify areas for improvement such as specificity, clarity, and relevance.\n",
    "- Consider the context and potential keywords that would optimize retrieval.\n",
    "- Maintain the intent of the original question while enhancing its structure and vocabulary.\n",
    "\n",
    "# Steps\n",
    "\n",
    "1. **Understand the Original Question**: Identify the core intent and any keywords.\n",
    "2. **Enhance Clarity**: Simplify language and ensure the question is direct and to the point.\n",
    "3. **Optimize for Retrieval**: Add or rearrange keywords for better alignment with vectorstore indexing.\n",
    "4. **Review**: Ensure the improved question accurately reflects the original intent and is free of ambiguity.\n",
    "\n",
    "# Output Format\n",
    "\n",
    "- Provide a single, improved question.\n",
    "- Do not include any introductory or explanatory text; only the reformulated question.\n",
    "\n",
    "# Examples\n",
    "\n",
    "**Input**: \n",
    "\"What are the benefits of using renewable energy sources over fossil fuels?\"\n",
    "\n",
    "**Output**: \n",
    "\"How do renewable energy sources compare to fossil fuels in terms of benefits?\"\n",
    "\n",
    "**Input**: \n",
    "\"How does climate change impact polar bear populations?\"\n",
    "\n",
    "**Output**: \n",
    "\"What effects does climate change have on polar bear populations?\"\n",
    "\n",
    "# Notes\n",
    "\n",
    "- Ensure the improved question is concise and contextually relevant.\n",
    "- Avoid altering the fundamental intent or meaning of the original question.\n",
    "\n",
    "\n",
    "[REMEMBER] Re-written question should be in the same language as the original question.\n",
    "\n",
    "# Here is the original question that needs to be rewritten:\n",
    "{question}\n",
    "\"\"\",\n",
    "    input_variables=[\"generation\", \"question\"],\n",
    ")\n",
    "\n",
    "question_rewriter = (\n",
    "    re_write_prompt | ChatOpenAI(model=\"gpt-4o-mini\", temperature=0) | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17426f56",
   "metadata": {},
   "source": [
    "생성한 `question_rewriter` 를 활용하여 질문을 재작성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "59b3d468",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'앤스로픽에 투자한 미국 기업은 어떤 곳들이 있나요?'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 질문 재작성\n",
    "question = \"앤스로픽에 투자한 미국기업\"\n",
    "\n",
    "question_rewriter.invoke({\"question\": question})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "63df9562",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Query Rewrite 노드\n",
    "def query_rewrite(state: GraphState) -> GraphState:\n",
    "    latest_question = state[\"question\"][-1].content\n",
    "    question_rewritten = question_rewriter.invoke({\"question\": latest_question})\n",
    "    return {\"question\": question_rewritten}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a3f7785d",
   "metadata": {},
   "source": [
    "## Edges\n",
    "\n",
    "- `Edges`: 현재 `State`를 기반으로 다음에 실행할 `Node`를 결정하는 Python 함수.\n",
    "\n",
    "일반 엣지, 조건부 엣지 등이 있습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "a6015807",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langgraph.graph import END, StateGraph\n",
    "from langgraph.checkpoint.memory import MemorySaver\n",
    "\n",
    "# 그래프 정의\n",
    "workflow = StateGraph(GraphState)\n",
    "\n",
    "# 노드 추가\n",
    "workflow.add_node(\"retrieve\", retrieve_document)\n",
    "workflow.add_node(\"relevance_check\", relevance_check)\n",
    "workflow.add_node(\"llm_answer\", llm_answer)\n",
    "workflow.add_node(\"web_search\", web_search)\n",
    "\n",
    "# Query Rewrite 노드 추가\n",
    "workflow.add_node(\"query_rewrite\", query_rewrite)\n",
    "\n",
    "# 엣지 추가\n",
    "workflow.add_edge(\"query_rewrite\", \"retrieve\")  # 질문 재작성 -> 검색\n",
    "workflow.add_edge(\"retrieve\", \"relevance_check\")  # 검색 -> 관련성 체크\n",
    "\n",
    "# 조건부 엣지를 추가합니다.\n",
    "workflow.add_conditional_edges(\n",
    "    \"relevance_check\",  # 관련성 체크 노드에서 나온 결과를 is_relevant 함수에 전달합니다.\n",
    "    is_relevant,\n",
    "    {\n",
    "        \"relevant\": \"llm_answer\",  # 관련성이 있으면 답변을 생성합니다.\n",
    "        \"not relevant\": \"web_search\",  # 관련성이 없으면 다시 검색합니다.\n",
    "    },\n",
    ")\n",
    "\n",
    "workflow.add_edge(\"web_search\", \"llm_answer\")  # 검색 -> 답변\n",
    "workflow.add_edge(\"llm_answer\", END)  # 답변 -> 종료\n",
    "\n",
    "# 그래프 진입점 설정\n",
    "workflow.set_entry_point(\"query_rewrite\")\n",
    "\n",
    "# 체크포인터 설정\n",
    "memory = MemorySaver()\n",
    "\n",
    "# 그래프 컴파일\n",
    "app = workflow.compile(checkpointer=memory)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d9a15c32",
   "metadata": {},
   "source": [
    "컴파일한 그래프를 시각화 합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2e09251d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_teddynote.graphs import visualize_graph\n",
    "\n",
    "visualize_graph(app)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "110daa16",
   "metadata": {},
   "source": [
    "## 그래프 실행\n",
    "\n",
    "- `config` 파라미터는 그래프 실행 시 필요한 설정 정보를 전달합니다.\n",
    "- `recursion_limit`: 그래프 실행 시 재귀 최대 횟수를 설정합니다.\n",
    "- `inputs`: 그래프 실행 시 필요한 입력 정보를 전달합니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4af077a6",
   "metadata": {},
   "source": [
    "검색 결과의 `relevance_check` 가 실패할 경우, 검색을 수행하여 웹 검색 결과를 제공합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "12129e2a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mquery_rewrite\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "\u001b[1;32mquestion\u001b[0m:\n",
      "앤스로픽에 대한 투자 금액은 얼마인가요?\n",
      "==================================================\n",
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mretrieve\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "\u001b[1;32mcontext\u001b[0m:\n",
      "<document><content>£구글, 앤스로픽에 최대 20억 달러 투자 합의 및 클라우드 서비스 제공\n",
      "n 구글이 2023년 10월 27일 앤스로픽에 최대 20억 달러를 투자하기로 합의했으며, 이 중 5억\n",
      "달러를 우선 투자하고 향후 15억 달러를 추가로 투자할 방침\n",
      "∙ 구글은 2023년 2월 앤스로픽에 이미 5억 5,000만 달러를 투자한 바 있으며, 아마존도 지난 9월\n",
      "앤스로픽에 최대 40억 달러의 투자 계획을 공개\n",
      "∙ 한편, 2023년 11월 8일 블룸버그 보도에 따르면 앤스로픽은 구글의 클라우드 서비스 사용을 위해\n",
      "4년간 30억 달러 규모의 계약을 체결</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>14</page></document>\n",
      "<document><content>1. 정책/법제 2. 기업/산업 3. 기술/연구 4. 인력/교육\n",
      "구글, 앤스로픽에 20억 달러 투자로 생성 AI 협력 강화\n",
      "KEY Contents\n",
      "n 구글이 앤스로픽에 최대 20억 달러 투자에 합의하고 5억 달러를 우선 투자했으며, 앤스로픽은\n",
      "구글과 클라우드 서비스 사용 계약도 체결\n",
      "n 3대 클라우드 사업자인 구글, 마이크로소프트, 아마존은 차세대 AI 모델의 대표 기업인\n",
      "앤스로픽 및 오픈AI와 협력을 확대하는 추세\n",
      "£구글, 앤스로픽에 최대 20억 달러 투자 합의 및 클라우드 서비스 제공</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>14</page></document>\n",
      "<document><content>4년간 30억 달러 규모의 계약을 체결\n",
      "∙ 오픈AI 창업자 그룹의 일원이었던 다리오(Dario Amodei)와 다니엘라 아모데이(Daniela Amodei)\n",
      "남매가 2021년 설립한 앤스로픽은 챗GPT의 대항마 ‘클로드(Claude)’ LLM을 개발\n",
      "n 아마존과 구글의 앤스로픽 투자에 앞서, 마이크로소프트는 차세대 AI 모델의 대표 주자인 오픈\n",
      "AI와 협력을 확대\n",
      "∙ 마이크로소프트는 오픈AI에 앞서 투자한 30억 달러에 더해 2023년 1월 추가로 100억 달러를</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>14</page></document>\n",
      "<document><content>투자하기로 하면서 오픈AI의 지분 49%를 확보했으며, 오픈AI는 마이크로소프트의 애저(Azure)\n",
      "클라우드 플랫폼을 사용해 AI 모델을 훈련\n",
      "£구글, 클라우드 경쟁력 강화를 위해 생성 AI 투자 확대\n",
      "n 구글은 수익률이 높은 클라우드 컴퓨팅 시장에서 아마존과 마이크로소프트를 따라잡고자 생성 AI를\n",
      "통한 기업 고객의 클라우드 지출 확대를 위해 AI 투자를 지속\n",
      "∙ 구글은 앤스로픽 외에도 AI 동영상 제작 도구를 개발하는 런웨이(Runway)와 오픈소스 소프트웨어\n",
      "기업 허깅 페이스(Hugging Face)에도 투자</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>14</page></document>\n",
      "<document><content>1,000만 달러 이상을 기부\n",
      "∙ 또한 신기술의 거버넌스와 안전 분야에서 전문성을 갖춘 브루킹스 연구소 출신의 크리스 메서롤(Chris\n",
      "Meserole)을 포럼의 상무이사로 임명\n",
      "n 최근 AI 기술이 급속히 발전하면서 AI 안전에 관한 연구가 부족한 시점에, 포럼은 이러한 격차를 해소\n",
      "하기 위해 AI 안전 기금을 조성\n",
      "∙ 참여사들은 지난 7월 백악관 주재의 AI 안전 서약에서 외부자의 AI 시스템 취약점 발견과 신고를\n",
      "촉진하기로 약속했으며, 약속을 이행하기 위해 기금을 활용해 외부 연구집단의 AI 시스템 평가에\n",
      "자금을 지원할 계획</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>10</page></document>\n",
      "<document><content>모델 평가 기법 개발에 자금을 중점 지원할 계획\n",
      "£프런티어 모델 포럼, 자선단체와 함께 AI 안전 연구를 위한 기금 조성\n",
      "n 구글, 앤스로픽, 마이크로소프트, 오픈AI가 출범한 프런티어 모델 포럼이 2023년 10월 25일 AI 안전\n",
      "연구를 위한 기금을 조성한다고 발표\n",
      "∙ 참여사들은 맥거번 재단(Patrick J. McGovern Foundation), 데이비드 앤 루실 패커드 재단(The\n",
      "David and Lucile Packard Foundation) 등의 자선단체와 함께 AI 안전 연구를 위한 기금에</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>10</page></document>\n",
      "<document><content>▹ 삼성전자, 자체 개발 생성 AI ‘삼성 가우스’ 공개 ···························································10\n",
      "▹ 구글, 앤스로픽에 20억 달러 투자로 생성 AI 협력 강화 ················································11\n",
      "▹ IDC, 2027년 AI 소프트웨어 매출 2,500억 달러 돌파 전망···········································12</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>2</page></document>\n",
      "<document><content>1. 정책/법제 2. 기업/산업 3. 기술/연구 4. 인력/교육\n",
      "미국 프런티어 모델 포럼, 1,000만 달러 규모의 AI 안전 기금 조성\n",
      "KEY Contents\n",
      "n 구글, 앤스로픽, 마이크로소프트, 오픈AI가 참여하는 프런티어 모델 포럼이 자선단체와 함께 AI\n",
      "안전 연구를 위한 1,000만 달러 규모의 AI 안전 기금을 조성\n",
      "n 프런티어 모델 포럼은 AI 모델의 취약점을 발견하고 검증하는 레드팀 활동을 지원하기 위한\n",
      "모델 평가 기법 개발에 자금을 중점 지원할 계획</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>10</page></document>\n",
      "<document><content>자금을 지원할 계획\n",
      "£AI 안전 기금으로 AI 레드팀을 위한 모델 평가 기법 개발을 중점 지원할 계획\n",
      "n 프런티어 모델 포럼은 AI 안전 기금을 통해 AI 레드팀 활동을 위한 새로운 모델 평가 기법의 개발을\n",
      "중점 지원할 예정\n",
      "∙ 포럼에 따르면 AI 레드팀에 대한 자금 지원은 AI 모델의 안전과 보안 기준의 개선과 함께 AI 시스템\n",
      "위험 대응 방안에 관한 산업계와 정부, 시민사회의 통찰력 확보에 도움이 될 전망으로, 포럼은 향후 몇\n",
      "달 안에 기금 지원을 위한 제안 요청을 받을 계획</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>10</page></document>\n",
      "<document><content>Forum and over $10 million for a new AI Safety Fund, 2023.10.25.</content><source>data/SPRI_AI_Brief_2023년12월호_F.pdf</source><page>10</page></document>\n",
      "==================================================\n",
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mrelevance_check\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "\u001b[1;32mrelevance\u001b[0m:\n",
      "yes\n",
      "==================================================\n",
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mllm_answer\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "\u001b[1;32manswer\u001b[0m:\n",
      "구글은 앤스로픽에 최대 20억 달러를 투자하기로 합의하였으며, 이 중 5억 달러를 우선 투자하고 향후 15억 달러를 추가로 투자할 계획입니다.\n",
      "\n",
      "**Source**\n",
      "- data/SPRI_AI_Brief_2023년12월호_F.pdf (page 14)\n",
      "('user', '앤스로픽에 대한 투자 금액은 얼마인가요?')\n",
      "('assistant', '구글은 앤스로픽에 최대 20억 달러를 투자하기로 합의하였으며, 이 중 5억 달러를 우선 투자하고 향후 15억 달러를 추가로 투자할 계획입니다.\\n\\n**Source**\\n- data/SPRI_AI_Brief_2023년12월호_F.pdf (page 14)')\n",
      "==================================================\n"
     ]
    }
   ],
   "source": [
    "from langchain_core.runnables import RunnableConfig\n",
    "from langchain_teddynote.messages import stream_graph, invoke_graph, random_uuid\n",
    "\n",
    "# config 설정(재귀 최대 횟수, thread_id)\n",
    "config = RunnableConfig(recursion_limit=10, configurable={\"thread_id\": random_uuid()})\n",
    "\n",
    "# 질문 입력\n",
    "inputs = GraphState(question=\"앤스로픽 투자 금액\")\n",
    "\n",
    "# 그래프 실행\n",
    "invoke_graph(app, inputs, config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "d315c594",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mquery_rewrite\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "앤스로픽에 대한 투자 금액은 얼마인가요?\n",
      "==================================================\n",
      "🔄 Node: \u001b[1;36mllm_answer\u001b[0m 🔄\n",
      "- - - - - - - - - - - - - - - - - - - - - - - - - \n",
      "구글은 앤스로픽에 최대 20억 달러를 투자하기로 합의하였으며, 이 중 5억 달러를 우선 투자하고 향후 15억 달러를 추가로 투자할 계획입니다.\n",
      "\n",
      "**Source**\n",
      "- data/SPRI_AI_Brief_2023년12월호_F.pdf (page 14)"
     ]
    }
   ],
   "source": [
    "# 그래프 실행\n",
    "stream_graph(app, inputs, config, [\"query_rewrite\", \"llm_answer\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "08e7f1d5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original Question: 앤스로픽 투자 금액\n",
      "Rewritten Question: 앤스로픽에 대한 투자 금액은 얼마인가요?\n",
      "============================================================\n",
      "Answer:\n",
      "구글은 앤스로픽에 최대 20억 달러를 투자하기로 합의하였으며, 이 중 5억 달러를 우선 투자하고 향후 15억 달러를 추가로 투자할 계획입니다.\n",
      "\n",
      "**Source**\n",
      "- data/SPRI_AI_Brief_2023년12월호_F.pdf (page 14)\n"
     ]
    }
   ],
   "source": [
    "# 최종 출력 확인\n",
    "outputs = app.get_state(config).values\n",
    "\n",
    "print(f'Original Question: {outputs[\"question\"][0].content}')\n",
    "print(f'Rewritten Question: {outputs[\"question\"][-1].content}')\n",
    "print(\"===\" * 20)\n",
    "print(f'Answer:\\n{outputs[\"answer\"]}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20440546",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
